﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace DetectSys.Infrastructure
{
    public class Exif
    {
        private struct PropertyItem
        {
            public int Tag;

            public int Type;

            public long Count;

            public byte[] Value;
        }

        private bool _Endian = false;

        private long _OffsetAPP1 = 0L;

        private long _OffsetTIFF = 0L;

        private long _OffsetIFD0 = 0L;

        private long _OffsetIFD1 = 0L;

        private long _OffsetExifIFD = 0L;

        private long _OffsetGPSIFD = 0L;

        private string _ImageFile = string.Empty;

        private List<PropertyItem> _Exif = new List<PropertyItem>();

        private int[] _PropertyItemTypeLength = new int[11]
        {
        0,
        1,
        1,
        2,
        4,
        8,
        8,
        4,
        8,
        4,
        8
        };

        public string ImageTitle
        {
            get
            {
                byte[] value = GetValue(270);
                if (value != null)
                {
                    return ASCIIToString(value);
                }
                return string.Empty;
            }
        }

        public string Make
        {
            get
            {
                byte[] value = GetValue(271);
                if (value != null)
                {
                    return ASCIIToString(value);
                }
                return string.Empty;
            }
        }

        public string Model
        {
            get
            {
                byte[] value = GetValue(272);
                if (value != null)
                {
                    return ASCIIToString(value);
                }
                return string.Empty;
            }
        }

        public string Software
        {
            get
            {
                byte[] value = GetValue(305);
                if (value != null)
                {
                    return ASCIIToString(value);
                }
                return string.Empty;
            }
        }

        public string DateTime
        {
            get
            {
                byte[] value = GetValue(36867);
                if (value != null)
                {
                    return ASCIIToString(value);
                }
                return string.Empty;
            }
        }

        public string XResolution
        {
            get
            {
                byte[] value = GetValue(282);
                if (value != null)
                {
                    return RationalToSingle(value, 0);
                }
                return string.Empty;
            }
        }

        public string YResolution
        {
            get
            {
                byte[] value = GetValue(283);
                if (value != null)
                {
                    return RationalToSingle(value, 0);
                }
                return string.Empty;
            }
        }

        public string ResolutionUnit
        {
            get
            {
                byte[] value = GetValue(296);
                if (value != null)
                {
                    return ShortToString(value, 0);
                }
                return string.Empty;
            }
        }

        public string orientation
        {
            get
            {
                byte[] value = GetValue(274);
                if (value != null)
                {
                    switch (ShortToString(value, 0))
                    {
                        case "1":
                            return "上/左";
                        case "2":
                            return "上/右";
                        case "3":
                            return "下/右";
                        case "4":
                            return "下/左";
                        case "5":
                            return "左/上";
                        case "6":
                            return "右/上";
                        case "7":
                            return "右/下";
                        case "8":
                            return "左/下";
                        default:
                            return "未知";
                    }
                }
                return string.Empty;
            }
        }
        public bool HaveCoord()
        {
            if (_Exif.Any(i=>i.Tag == 2)&&_Exif.Any(i=>i.Tag == 4))
            {
                return true;
            }
            else
            {
                return false;
            }
        }
        public string GPSLatitude
        {
            get
            {
                byte[] value = GetValue(2);
                if (value != null)
                {
                    return $"{RationalToSingle(value, 0)}°{RationalToSingle(value, 8)}'{RationalToSingle(value, 16)}\"";
                }
                return string.Empty;
            }
        }

        public double GPSAlt
        {
            get
            {
                byte[] value = GetValue(6);
                if (value != null)
                {
                    float num = BitConverter.ToInt32(MM2II(value, 0, 4), 0);
                    float num2 = BitConverter.ToInt32(MM2II(value, 0 + 4, 4), 0);
                    return (num / num2);
                }
                return double.NaN;
            }
        }
		private double RationalToDouble(byte[] b, int startindex)
		{
			float num = BitConverter.ToInt32(MM2II(b, startindex, 4), 0);
			float num2 = BitConverter.ToInt32(MM2II(b, startindex + 4, 4), 0);
			return (num / num2);
		}
		public double GPSLong
		{
			get
			{
				byte[] value = GetValue(4);
				if (value.Length == 24)
				{
					//degrees(将byte[0]~byte[3]转成uint, 除以byte[4]~byte[7]转成的uint) ??
					double d = RationalToDouble(value, 0);
					//minutes(將byte[8]~byte[11]转成uint, 除以byte[12]~byte[15]转成的uint) ??
					double m = RationalToDouble(value, 8);
					//seconds(將byte[16]~byte[19]转成uint, 除以byte[20]~byte[23]转成的uint) ??
					double s = RationalToDouble(value, 16);
					//计算经纬度数值, 如果是南纬, 要乘上(-1)   
					double dblGPSLatitude = (((s / 60 + m) / 60) + d);
					return dblGPSLatitude;
				}

				throw new ArgumentException();
			}
		}
		public double GpsLat
		{
			get
			{
				byte[] value = GetValue(2);
				if (value.Length == 24)
				{
					//degrees(将byte[0]~byte[3]转成uint, 除以byte[4]~byte[7]转成的uint) ??
					double d = RationalToDouble(value, 0);
					//minutes(將byte[8]~byte[11]转成uint, 除以byte[12]~byte[15]转成的uint) ??
					double m = RationalToDouble(value, 8);
					//seconds(將byte[16]~byte[19]转成uint, 除以byte[20]~byte[23]转成的uint) ??
					double s = RationalToDouble(value, 16);
					//计算经纬度数值, 如果是南纬, 要乘上(-1)   
					double dblGPSLat = (((s / 60 + m) / 60) + d);
					return dblGPSLat;
				}

				throw new ArgumentException();
			}
		}
        public string GPSLongitude
        {
            get
            {
                byte[] value = GetValue(4);
                if (value != null)
                {
                    return $"{RationalToSingle(value, 0)}°{RationalToSingle(value, 8)}'{RationalToSingle(value, 16)}\"";
                }
                return string.Empty;
            }
        }

        public string GPSAltitude
        {
            get
            {
                byte[] value = GetValue(6);
                if (value != null)
                {
                    return RationalToSingle(value, 0) + "m";
                }
                return string.Empty;
            }
        }

       

        public Exif(string imagefile)
        {
            if (string.IsNullOrEmpty(imagefile) || !File.Exists(imagefile))
            {
                throw new FileNotFoundException(_ImageFile + "加载失败，请检查路径后再次尝试。");
            }
            _ImageFile = imagefile;
            byte[] array = new byte[2];
            byte[] array2 = new byte[4];
            byte[] array3 = new byte[12];
            using (FileStream fileStream = new FileStream(_ImageFile, FileMode.Open, FileAccess.Read))
            {
                while (fileStream.Read(array, 0, array.Length) > 0)
                {
                    if (array[0] == byte.MaxValue && array[1] == 225)
                    {
                        _OffsetAPP1 = fileStream.Position - 2;
                        break;
                    }
                }
                if (_OffsetAPP1 > 0)
                {
                    fileStream.Seek(2L, SeekOrigin.Current);
                    if (fileStream.Read(array2, 0, array2.Length) > 0)
                    {
                        if (Encoding.ASCII.GetString(array2) == "Exif")
                        {
                            fileStream.Seek(2L, SeekOrigin.Current);
                            _OffsetTIFF = fileStream.Position;
                            if (fileStream.Read(array, 0, array.Length) > 0)
                            {
                                if (array[0] == 73 && array[1] == 73)
                                {
                                    _Endian = true;
                                }
                                else
                                {
                                    _Endian = false;
                                }
                                fileStream.Seek(2L, SeekOrigin.Current);
                                if (fileStream.Read(array2, 0, array2.Length) > 0)
                                {
                                    _OffsetIFD0 = BitConverter.ToInt32(MM2II(array2), 0);
                                    fileStream.Seek(_OffsetTIFF + _OffsetIFD0, SeekOrigin.Begin);
                                    if (fileStream.Read(array, 0, array.Length) > 0)
                                    {
                                        int num = BitConverter.ToInt16(MM2II(array), 0);
                                        for (int i = 0; i < num; i++)
                                        {
                                            if (fileStream.Read(array3, 0, array3.Length) > 0)
                                            {
                                                int num2 = BitConverter.ToUInt16(MM2II(array3, 0, 2), 0);
                                                int num3 = BitConverter.ToInt32(MM2II(array3, 8, 4), 0);
                                                switch (num2)
                                                {
                                                    case 34665:
                                                        _OffsetExifIFD = num3;
                                                        break;
                                                    case 34853:
                                                        _OffsetGPSIFD = num3;
                                                        break;
                                                }
                                            }
                                        }
                                        if (fileStream.Read(array2, 0, array2.Length) > 0)
                                        {
                                            _OffsetIFD1 = BitConverter.ToUInt32(MM2II(array2), 0);
                                        }
                                    }
                                }
                            }
                        }
                        else
                        {
                            fileStream.Dispose();
                        }
                    }
                }
                else
                {
                    fileStream.Dispose();
                }
            }
            if (_OffsetIFD0 > 0)
            {
                ParseIFD(_OffsetTIFF + _OffsetIFD0);
            }
            if (_OffsetExifIFD > 0)
            {
                ParseIFD(_OffsetTIFF + _OffsetExifIFD);
            }
            if (_OffsetGPSIFD > 0)
            {
                ParseIFD(_OffsetTIFF + _OffsetGPSIFD);
            }
        }

        private void ParseIFD(long offset)
        {
            byte[] array = new byte[2];
            byte[] array4 = new byte[4];
            byte[] array2 = new byte[12];
            using (FileStream fileStream = new FileStream(_ImageFile, FileMode.Open,FileAccess.Read))
            {
                fileStream.Seek(offset, SeekOrigin.Begin);
                if (fileStream.Read(array, 0, array.Length) > 0)
                {
                    int num = BitConverter.ToInt16(MM2II(array), 0);
                    for (int i = 0; i < num; i++)
                    {
                        if (fileStream.Read(array2, 0, array2.Length) > 0)
                        {
                            PropertyItem propertyItem = default(PropertyItem);
                            propertyItem.Tag = BitConverter.ToUInt16(MM2II(array2, 0, 2), 0);
                            propertyItem.Type = BitConverter.ToUInt16(MM2II(array2, 2, 2), 0);
                            propertyItem.Count = BitConverter.ToUInt32(MM2II(array2, 4, 4), 0);
                            if (propertyItem.Type <= 12)
                            {
                                if (_PropertyItemTypeLength[propertyItem.Type] * propertyItem.Count <= 4)
                                {
                                    propertyItem.Value = new byte[4]
                                    {
                                    array2[8],
                                    array2[9],
                                    array2[10],
                                    array2[11]
                                    };
                                }
                                else
                                {
                                    long position = fileStream.Position;
                                    long num2 = BitConverter.ToUInt32(MM2II(array2, 8, 4), 0);
                                    fileStream.Seek(_OffsetTIFF + num2, SeekOrigin.Begin);
                                    byte[] array3 = new byte[_PropertyItemTypeLength[propertyItem.Type] * propertyItem.Count];
                                    fileStream.Read(array3, 0, array3.Length);
                                    propertyItem.Value = array3;
                                    fileStream.Seek(position, SeekOrigin.Begin);
                                }
                            }
                            _Exif.Add(propertyItem);
                        }
                    }
                }
            }
        }

        private byte[] GetValue(int id)
        {
            for (int i = 0; i < _Exif.Count; i++)
            {
                if (_Exif[i].Tag == id)
                {
                    return _Exif[i].Value;
                }
            }
            return null;
        }

        private string ByteToString(byte[] b, int startindex)
        {
            char c = (char)b[startindex];
            return c.ToString();
        }

        private string ShortToString(byte[] b, int startindex)
        {
            return BitConverter.ToInt16(MM2II(b, startindex, 2), 0).ToString();
        }

        private string RationalToSingle(byte[] b, int startindex)
        {
            float num = BitConverter.ToInt32(MM2II(b, startindex, 4), 0);
            float num2 = BitConverter.ToInt32(MM2II(b, startindex + 4, 4), 0);
            return (num / num2).ToString();
        }

        private string ASCIIToString(byte[] b)
        {
            if (b[4] != 47)
            {
            }
            b[4] = (b[7] = 47);
            return Encoding.ASCII.GetString(b).Trim(default(char));
        }

        private byte[] MM2II(byte[] b)
        {
            return MM2II(b, 0, b.Length);
        }

        private byte[] MM2II(byte[] b, int index, int length)
        {
            byte[] array = new byte[length];
            if (_Endian)
            {
                for (int i = index; i < index + length; i++)
                {
                    array[i - index] = b[i];
                }
            }
            else
            {
                for (int j = index; j < index + length; j++)
                {
                    array[j - index] = b[index + length - (j - index) - 1];
                }
            }
            return array;
        }

        public override string ToString()
        {
            return $"标题：{ImageTitle}\r\n厂商：{Make}\r\n机型：{Model}\r\n软件：{Software}\r\n时间：{DateTime}\r\n水平分辨率：{XResolution}\r\n垂直分辨率：{YResolution}\r\nGPS纬度：{GPSLatitude}\r\nGPS经度：{GPSLongitude}\r\nGPS高度：{GPSAltitude}\r\n";
        }
    }

}
